#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 时间     : 2018-09-25 14:48:57
# 作者     : DL (584054432@qq.com)
# 网址     : https://gitee.com/dingliangPython/
# 软件版本 : Python3.6.5
# 功能     ： Python框架

import os,sys,time,re,snap7,shutil,csv,queue,threading,csv,struct,requests,json
import apscheduler
import ReadConfig,log,ToTime
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
import RWxml,gcl_sdk,s7Parser
import OracleConnect,MysqlConnect,MssqlConnect,AccessConnect,SqliteConnect
from suds.client import Client as wcf
from psping import Ping


class main(object):
    """docstring for main"""
    def __init__(self):
        super(main, self).__init__()
        self.config          =ReadConfig.Config("./config/config.ini").read()
        #日志
        self.log             =log.Logger(self.config['Log'])

        self.Set_Termianal_Window_Size()
        #脚本路径
        self.scriptPath      =self.config['Modules']['scripts']
        #自动增加任务扫描周期
        self.scriptScanCycle =int(self.config['Modules']['scriptScanCycle'])
        self.addModulesPath()
        # 调度任务实例后台类型
        self.schedudler      =BackgroundScheduler()
        #数据库连接
        self.DBconn          =self.GetDBConnect()
        #PLC 连接
        self.PLCconn         =self.GetPlcConnect()
        #busSDK
        self.sdk             =self.GetSdk()
        #全局变量
        self.Global          ={'FLAG':0}
        #实例Ping
        self.ping            =Ping(self.log)

        #运行调度任务
        self.runTask()
    #设置终端窗口大小
    def Set_Termianal_Window_Size(self,width=140,high=60 ,buffer_line=1000):
        '''[summary]

        [设置终端窗口大小及缓冲区行数据 ]

        Args:
            width: [宽] (default: {140})
            high: [高] (default: {60})
            buffer_line: [缓冲区行数] (default: {1000})
        '''
        try:
            Modules=self.config.get('Modules')
            high=int(Modules.get('high')) if Modules.get('high') else high
            width=int(Modules.get('width')) if Modules.get('width') else width
            buffer_line=int(Modules.get('buffer_line')) if Modules.get('buffer_line') else buffer_line
            setWindowSize='reg add "HKEY_CURRENT_USER\Console" /t REG_DWORD /v ScreenBufferSize /d 0x%04x%04x /f '%(high,width)
            setWindowBuffer='reg add "HKEY_CURRENT_USER\Console" /t REG_DWORD /v ScreenBufferSize /d 0x%04x%04x /f '%(buffer_line,width)
            os.system(setWindowSize)
            self.log.info("设置终端窗口，宽：%s，高：%s"%(width,high))
            os.system(setWindowBuffer)
            self.log.info("设置终端窗口缓冲行数，宽：%s，缓冲行数：%s"%(width,buffer_line))
            self.log.info("设置终端窗口大小及缓冲区行数成功，下次启动程序生效")
        except Exception as e:
            self.log.info("设置终端窗口大小及缓冲区行数失败")
    #获取SDK
    def GetSdk(self):
        #try:
            if self.config.get('SDK') and self.config.get('SDK').get('on-off'):
                sdk=gcl_sdk.RtData(self.log)
                self.log.info("连接BUS SDK 成功")
                return sdk
        # except Exception as e:
        #     self.log.error("连接BUS SDK 失败")
    #获取连接PLC
    def GetPlcConnect(self):
        #检查系统是否存在库文件
        dll32=[r'C:\Windows\System32\snap7.dll',r'C:\Windows\System32\snap7.lib']
        for dll in dll32:
            if not os.path.exists(dll):
                self.log.warning("s7库文件不存在，拷贝文件到系统目录")
                rec=shutil.copyfile(os.path.split(dll)[1],dll)
                self.log.info("拷贝文件成功：%s"%rec)
            else:
                self.log.info("s7库文件已存在")
        #连接
        plcinfo ={}
        a       =[ plcinfo.setdefault(v,self.config[v]) if self.config[v].get('on-off') else None for v in self.config.keys() if re.findall('PLC',v)]
        connect ={}
        if plcinfo :
            for name in plcinfo.keys():
                c          =plcinfo.get(name)
                Type       =c.get('type')
                ip         =c.get('ip')
                rack       =int(c.get('rack'))
                slot       =int(c.get('slot'))
                port       =int(c.get('port'))
                localTsap  = int( c['localTsap'])if c.get('localTsap') else 768
                remoteTsap =int(c['remoteTsap'])if c.get('remoteTsap') else 512
                try:
                    client = snap7.client.Client()
                    if Type and int(Type)<1200:
                        self.log.info("PLC型号：%s,本地TSAP:%#x,远程TSAP：%#x"%(Type,localTsap,remoteTsap))
                        client.set_connection_params(ip, localTsap, remoteTsap)
                    client.connect(ip,rack,slot,port)
                    client.S7Parser=s7Parser.S7Parser(client,self.log)
                    connect.setdefault(name,client)
                    self.log.info("PLC: %s-%s 连接成功"%(name,ip))
                except Exception as e:
                    print('\n')
                    self.log.error("PLC: %s 连接失败，请检查连接信息:%s\n"%(name,c))
                    connect.setdefault(name,None)
        self.plc_Info=plcinfo
        return connect
    #连接PLC
    def PLC_conn(self,name):
        self.log.info("执行PLC连接断线重连%s"%('.'*40))
        try:
            c          =self.plc_Info.get(name)
            Type       =c.get('type')
            ip         =c.get('ip')
            rack       =int(c.get('rack'))
            slot       =int(c.get('slot'))
            port       =int(c.get('port'))
            localTsap  = int( c['localTsap'])if c.get('localTsap') else 768
            remoteTsap =int(c['remoteTsap'])if c.get('remoteTsap') else 512
            client = snap7.client.Client()
            if Type and int(Type)<1200:
                self.log.info("PLC型号：%s,本地TSAP:%#x,远程TSAP：%#x"%(Type,localTsap,remoteTsap))
                client.set_connection_params(ip, localTsap, remoteTsap)
            client.connect(ip,rack,slot,port)
            client.S7Parser=s7Parser.S7Parser(client,self.log)
            self.PLCconn[name]=client
            self.log.info("PLC: %s-%s 重新连接成功"%(name,ip))
            return client
        except Exception as e:
            print('\n')
            self.log.error("重新连接:%s 失败，请检查连接信息\n"%name)
    #PLC连接重连
    def reset_PLC_conn(self):
        for name in self.PLCconn.keys():
            conn=self.PLCconn[name]
            conn=conn.get_connected() if self.PLCconn[name] else self.PLC_conn(name)
            conn=conn if conn else self.PLC_conn(name)
    #连接数据库
    def GetDBConnect(self):
        DBconn={
                "Oracle":{},
                "Mysql":{},
                "Mssql":{},
                "Access":{},
                "Sqlite":{}
                }

        DBconfig=[v for v in self.config.values() if v.get('type') in('Oracle','Mssql','Access','Mysql','Sqlite') ]
        if DBconfig and len(DBconfig)>=1:
            for v in DBconfig:
                if re.findall('\w+',v.get('type'))[0]=='Oracle' :
                    Oracle=None
                    if v.get('pool'):
                        self.log.info("创建Oracle数据库连接池")
                        Oracle=OracleConnect.PoolConnect(v,self.log)
                    else:
                        self.log.info("创建Oracle数据库连接")
                        Oracle=OracleConnect.Connect(v,self.log)
                    DBconn['Oracle'].setdefault(v.get('id'),Oracle)
                elif re.findall('\w+',v.get('type'))[0]=='Mssql' :
                    Mssql=None
                    if v.get('pool'):
                        self.log.info("创建Mssql数据库连接池")
                        Mssql=MssqlConnect.PoolConnect(v,self.log)
                    else:
                        self.log.info("创建Mssql数据库连接")
                        Mssql=MssqlConnect.Connect(v,self.log)
                    DBconn['Mssql'].setdefault(v.get('id'),Mssql)
                elif re.findall('\w+',v.get('type'))[0]=='Mysql' :
                    Mysql=None
                    if v.get('pool'):
                        self.log.info("创建Mysql数据库连接池")
                        Mysql=MysqlConnect.PoolConnect(v,self.log)
                    else:
                        self.log.info("创建Mysql数据库连接")
                        Mysql=MysqlConnect.Connect(v,self.log)
                    DBconn['Mysql'].setdefault(v.get('id'),Mysql)
                elif re.findall('\w+',v.get('type'))[0]=='Sqlite' :
                    Sqlite=None
                    if v.get('pool'):
                        self.log.info("创建Sqlite数据库连接池")
                        Sqlite=SqliteConnect.PoolConnect(v,self.log)
                    else:
                        self.log.info("创建Sqlite数据库连接")
                        Sqlite=SqliteConnect.Connect(v,self.log)
                    DBconn['Sqlite'].setdefault(v.get('id'),Sqlite)
                elif re.findall('\w+',v.get('type'))[0]=='Access' :
                    Access=None
                    if v.get('pool'):
                        self.log.info("创建Access数据库连接池")
                        Access=AccessConnect.PoolConnect(v,self.log)
                    else:
                        self.log.info("创建Access数据库连接")
                        Access=AccessConnect.Connect(v,self.log)
                    DBconn['Access'].setdefault(v.get('id'),Access)
        return DBconn
    #脚本直接能使用的模块、类、方法、函数、变量等
    def globalObj(self):
        g={}

        g.setdefault('os',os)
        g.setdefault('time',time)
        g.setdefault('sys',sys)
        g.setdefault('re',re)
        g.setdefault('csv',csv)
        g.setdefault('snap7',snap7)
        g.setdefault('queue',queue)
        g.setdefault('threading',threading)
        g.setdefault('struct',struct)

        g.setdefault('Get_info',self.Get_info)
        g.setdefault('configInfo',dict(self.config))
        g.setdefault('writeLog',self.log.write)
        g.setdefault('log',self.log)
        g.setdefault('ToTime',ToTime.ToTime)
        g.setdefault('xml',RWxml.xml)
        g.setdefault('wcf',wcf)
        g.setdefault('Global',self.Global)
        g.setdefault('ping',self.ping.scan_ip)
        g.setdefault('pingFile',self.ping.scan_file)

        g.setdefault('dbConnect',self.DBconn)
        g.setdefault('PLC',self.PLCconn)
        g.setdefault('sdk',self.sdk)
        g.setdefault('post_measure',self.post_measure)



        return g
    #webpost查询实时数据
    def post_measure(self,mid):
        host=self.config['SDK'].get('RtHost','127.0.0.1')
        port=int(self.config['SDK'].get('RtPort','8000'))
        rtHost="http://{}:{}/ics/ics.cgi?fncode=req.rtdata_v101&filetype=json".format(host,port)
        midList=mid  if isinstance(mid,list) or isinstance(mid,tuple) else [mid]
        #post 请求
        Temp={
            "session": "sbid=0001;xxx=adfadsf",
            "structtype": "rtdata_v101",
            "params": []}
        #组合nginx请求数据
        _=[Temp['params'].append({"url":" ","mid":int(v)}) for v in midList]
        self.log.debug('请求实时数据： %s'%Temp)
        try:
            post=requests.post(rtHost,json=Temp)
            rec=post.status_code
            if rec==200:
                data=post.json()
                return data['data'][0] if len(data['data'])==1 else data['data']
            else:
                self.log.warning("web接口接收错误异常")
        except Exception as e:
            self.log.error("通过web接口查询实时数据异常，%s"%e)
    #查询函数文档
    def Get_info(self,func):
        info=(func.__doc__)
        print(info)
        return info
    #动态增加任务
    def dynamicAddScript(self):
        taskList=[v.id for v in self.schedudler.get_jobs()]
        scriptList=self.getScripts()
        if len(taskList)>=1 and len(scriptList)>=1:
            for task in scriptList:
                taskName=os.path.split(task)[1]
                if taskName not in  taskList:
                    self.log.info("这是新增加的任务调度：%s"%taskName)
                    cycle=int(re.findall('\w+\.(\d+)',taskName)[0]) or 5
                    trigger=IntervalTrigger(seconds=cycle)
                    self.schedudler.add_job(self.execPyFile,trigger,args=(task,),id=taskName)
    #获取运行脚本 os.path.splitext(v)[0]
    def getScripts(self):
        scriptList=[]
        for file in os.listdir(self.scriptPath):
            if os.path.isfile(os.path.join(self.scriptPath,file)) and re.findall('\w+\.\d+s\.\w+',file):
                file=os.path.abspath(os.path.join(self.scriptPath,file))
                fileOpen=open(file,encoding='utf-8')
                with open(file,encoding='utf-8') as f:
                    if len(f.read())>=1:
                        scriptList.append(file)
        return scriptList
    #增加运行模块脚步进环境变量
    def addModulesPath(self):
        sys.path.append(os.path.abspath(self.config['Modules']['modules']))
        #sys.path.append(self.config['Modules']['scripts'])
    #任务异常
    def taskError(self,event):
        if event.exception or event.traceback:
            self.log.error("脚本：%s,异常日志：%s"%(event.job_id,event.traceback.replace('<string>','./scripts/'+event.job_id)))
        if event.exception and self.config['Modules'].get('isKill'):
            if not self.config['Modules'].get('killName'):
                self.config['Modules'].setdefault('killName','TerminalMain')
            self.log.error("%s任务执行出错：%s"%('*'*20,event.exception))
            self.log.error("%s任务执行出错：退出程序:%s "%('*'*20,self.config['Modules'].get('killName')))
            os.system("TASKKILL /F /IM %s.exe"%self.config['Modules'].get('killName'))
            sys.exit(1)
    #自动增加任务
    def runTask(self):
        scriptList=self.getScripts()
        if scriptList and len(scriptList)>=1:
            for v in scriptList:
                fileName=os.path.split(v)[1]
                cycle=int(re.findall('\w+\.(\d+)',fileName)[0]) or 5
                trigger=IntervalTrigger(seconds=cycle)
                #增加任务实例数防止任务比运行时间长，跳过调度,max_instances
                self.schedudler.add_job(self.execPyFile,trigger,args=(v,),id=fileName,max_instances=10)
                self.log.info("增加调度任务：%s"%fileName)
            self.schedudler.add_job(self.dynamicAddScript,IntervalTrigger(seconds=self.scriptScanCycle),id='动态加载脚本',max_instances=10)
            self.schedudler.add_job(self.reset_PLC_conn,IntervalTrigger(seconds=20),id='PLC连接断线重连',max_instances=10)
            #任务异常
            self.schedudler.add_listener(self.taskError,EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
            # 任务启动
            self.schedudler.start()
        else:
            self.log.warning("脚本目录：%s,没有脚本任务"%self.scriptPath)
    #执行脚本文件
    def execPyFile(self,file):
        print('\n')
        taskName=os.path.split(file)[1]
        self.log.info("当前执行的任务是：%s"%taskName)
        beginTime=time.clock()
        g=self.globalObj()
        with open(file,encoding= 'utf-8') as fileOpen:
            PyFile=fileOpen.read()
            if len(PyFile)>=1:
                a=exec(PyFile,g)

        self.log.info("%s 执行耗时：%s秒"%(taskName,time.clock()-beginTime))
if __name__ == '__main__':
    #实例主程序
    app=main()
    #程序标题名称
    os.system("title %s"%app.config['Modules']['terminalName'])
    while True:
        print("\n\n")
        app.log.info("%s当前正在运行的调度任务数量：%s"%('*'*30,len(app.schedudler.get_jobs())))
        app.log.info("%s当前正在运行的调度任务：%s"%('*'*30,[v.id for v in app.schedudler.get_jobs()]))
        if len(app.schedudler.get_jobs())<=0:
            app.log.warning("%s没有调度的任务\n\n"%("-"*30))
        time.sleep(app.scriptScanCycle*10)
